#include "../FCNN/FCNN.h"
#include "Chromosome.h"
#include "ChromosomeFactory.h"
#include "Utils/GlobalCppRandomEngine.h"
#include <cmath>
#include <iostream>

namespace GeneticAlgorithm {

    Chromosome::Chromosome(unsigned long lengthOfChromosome) {
        this->dataArray = new long double[lengthOfChromosome];
        this->lengthOfData = lengthOfChromosome;

        using namespace FCNN;
        unsigned long fcnnLayerTotal = 3;
        unsigned long fcnnLayerConfig[] = {6, 10, 6};
        unsigned long sequenceLength = 17;
        long double sequence[] = {0, -1, -1, -1, 0, -1, 1, 1, 0, 1, -1, 1, 0, 1, 1, -1, 0};
        this->fcnn = new FCNNModel(fcnnLayerTotal, fcnnLayerConfig);
        this->sequenceNetwork = new SequenceNetwork(this->fcnn, sequence, sequenceLength);
        //std::cout << "TrainValues=" << fcnn.getTrainableValueNumber() << std::endl;throw new Exception("Finish.");
    }

    Chromosome::~Chromosome() {
        if (nullptr != this->cacheDataForCrossover) {
            delete[] this->cacheDataForCrossover;
        }
        delete[] this->dataArray;
        delete this->sequenceNetwork;
        delete this->fcnn;
    }

    bool Chromosome::setGene(unsigned long offset, long double value) {
        if (offset > this->lengthOfData - 1) {
            return false;
        }
        if (this->dataArray[offset] != value) {
            this->dataArray[offset] = value;
            this->isFitnessCached = false;
        }
        return true;
    }

    long double Chromosome::getGene(unsigned long offset) {
        if (offset > this->lengthOfData - 1) {
            throw "Error, out of range.";
        }
        return this->dataArray[offset];
    }

    void Chromosome::dump() {
        std::cout << this->getFitness() << std::endl;
    }

    unsigned long Chromosome::getLength() {
        return this->lengthOfData;
    }

    long double Chromosome::getFitness() {
        if (this->isFitnessCached) {
            return this->fitnessCached;
        }
        long double error;
        for (unsigned long i = 0; i < this->lengthOfData; i++) {
            this->fcnn->setTrainableValue(i, this->dataArray[i]);
        }
        error = this->sequenceNetwork->getError();
        this->isFitnessCached = true;
        this->fitnessCached = 1.0 / (error + 0.01);
        return this->fitnessCached;
    }

    Chromosome* Chromosome::crossover(Chromosome* another) {
        if (another->getLength() != this->lengthOfData) {
            throw "Length not equals!";
        }
        if (nullptr == this->cacheDataForCrossover) {
            this->cacheDataForCrossover = new long double[this->lengthOfData];
        }
        for (unsigned long i = 0; i < this->lengthOfData; i++) {
            this->cacheDataForCrossover[i] = (this->dataArray[i] + another->getGene(i)) / 2.0;
        }
        Chromosome* newChromosome = ChromosomeFactory().buildFromArray(this->cacheDataForCrossover, this->lengthOfData);
        return newChromosome;
    }

    Chromosome* Chromosome::crossoverTo(Chromosome* another, Chromosome* replacement) {
        if (another->getLength() != this->lengthOfData || replacement->getLength() != this->lengthOfData) {
            throw "Length not equals!";
        }
        for (unsigned long i = 0; i < this->lengthOfData; i++) {
            replacement->setGene(i, (this->dataArray[i] + another->getGene(i)) / 2.0);
        }
        return replacement;
    }

    void Chromosome::copyDataFrom(Chromosome* another) {
        if (another->getLength() != this->lengthOfData) {
            throw "Length not equals!";
        }
        for (unsigned long i = 0; i < this->lengthOfData; i++) {
            this->dataArray[i] = another->getGene(i);
        }
        this->isFitnessCached = true;
        this->fitnessCached = another->getFitness();
    }

    void Chromosome::mutation(long double r) {
        if (r <= 0.0) {
            return;
        }
        using GeneticAlgorithm::Utils::GlobalCppRandomEngine;
        std::normal_distribution<long double> distribution(0, r);
        for (unsigned long i = 0; i < this->lengthOfData; i++) {
            this->dataArray[i] += distribution(GlobalCppRandomEngine::engine);
        }
        this->isFitnessCached = false;
    }
}
